import os
import  jieba
import pandas as pd
from collections import Counter
# 导入用于对象保存与加载的joblib
from sklearn.externals import joblib
# 导入keras中的词汇映射器Tokenizer
from keras.preprocessing.text import Tokenizer
# 导入从样本csv到内存的get_data_labels函数
# 限制句子的最小字符数和句子的最大字符数
csv_path = "./movie/sample.csv"

MIN_LENGTH = 5
MAX_LENGTH = 500

def get_p_text_list(single_article_path):
    """获取单篇文章的文本列表"""
    with open(single_article_path, "r") as f:
        text = f.read()
        # 去掉换行符, 并以句号划分
        cl = text.replace("\n", ".").split("。")
        # 过滤掉长度范围之外的句子
        cl = list(filter(lambda x: MIN_LENGTH<len(x)<MAX_LENGTH, cl))
    return cl


def get_p_sample(a_path, p_path):
    """该函数用于获得正样本的csv, 以文章路径和正样本csv写入路径为参数"""
    if not os.path.exists(a_path):
        return
    if not os.path.exists(p_path):
        os.mkdir(p_path)
    # 以追加的方式打开预写入正样本的csv
    fp = open(os.path.join(p_path, "p_sample.csv"), "a")
    # 遍历文章目录下的每一篇文章
    for u in os.listdir(a_path):
        cl = get_p_text_list(os.path.join(a_path, u))
        for clc in cl:
            fp.write("1" + "\t" + clc + "\n")  #进行样本的写入操作
    fp.close()


def get_sample(p_path, n_path_csv_list: list):
    """该函数用于获取样本集包括正负样本, 以正样本csv文件路径和负样本csv文件路径列表为参数"""
    fp = open(os.path.join(p_path, "sample.csv"), "w")
    with open(os.path.join(p_path, "p_sample.csv"), "r") as f:
        text = f.read()
    # 先将正样本写入样本csv之中
    fp.write(text)
    # 遍历负样本的csv列表
    for n_p_c in n_path_csv_list:
        with open(n_p_c, "r") as f:
            # 将其中的标签1改写为0
            text = f.read().replace("1", "0")
        # 然后写入样本的csv之中
        fp.write(text)
    fp.close()


def get_data_labels(csv_path):
    """获得训练数据和对应的标签, 以正负样本的csv文件路径为参数"""
    # 使用pandas读取csv文件至内存
    df = pd.read_csv(csv_path, header=None, sep="\t")

    # 对句子进行分词处理并过滤掉长度为1的词
    train_data = list(map(lambda x: list(filter(lambda x: len(x)>1, jieba.lcut(x))), df[1].values))
    # 取第0列的值作为训练标签
    train_labels = df[0].values
    return train_data, train_labels


def pic_show(pic, pic_path, pic_name):
    """用于图片显示，以图片对象和预保存的路径为参数"""
    if not os.path.exists(pic_path):
        os.mkdir(pic_path)
    pic.savefig(os.path.join(pic_path, pic_name))
    print("请通过地址http://47.92.175.143:8087/text_labeled/model_train" + pic_path[1:] + pic_name + "查看.")


def get_labels_distribution(train_labels, pic_path, pic_name="ld.png"):
    """获取正负样本数量的基本分布情况"""         # 只需要对标签进行统计即可
    # class_dict >>> {1: 3995, 0: 4418}
    class_dict = dict(Counter(train_labels))
    print(class_dict)
    df = pd.DataFrame(list(class_dict.values()), list(class_dict.keys()))
    pic = df.plot(kind='bar', title="类别分布图").get_figure()
    pic_show(pic, pic_path, pic_name)


def get_sentence_length_distribution(train_data, pic_path, pic_name="sld.png"):  #获取句子长度分布过程的代码分析
    """该函数用于获得句子长度分布情况"""
    sentence_len_list = list(map(len, train_data))
    # len_dict >>> {38: 62, 58: 18, 40: 64, 35: 83,....}
    len_dict = dict(Counter(sentence_len_list))
    len_list = list(zip(len_dict.keys(), len_dict.values()))
    # len_list >>> [(1, 3), (2, 20), (3, 51), (4, 96), (5, 121), (6, 173), ...]
    len_list.sort(key=(lambda x: x[0]))
    df = pd.DataFrame(list(map(lambda x: x[1], len_list)), list(
        map(lambda x: x[0], len_list)))
    ax = df.plot(kind='bar', figsize=(18, 18), title="句子长度分布图")
    ax.set_xlabel("句子长度")
    ax.set_ylabel("该长度出现的次数")
    pic = ax.get_figure()
    pic_show(pic, pic_path, pic_name)

#但在0-60的长度之间, 已经包含了超过90%的句子, 因此这里可以认为60的长度是一个合理的截断对齐长度, 即不会使大量句子被截断而失去主要信息,
#又能够有效避免补齐的特征数量太多, 导致模型参数过大.


def word_map(csv_path, tokenizer_path, cut_num):
    """进行词汇映射，以训练数据的csv路径和映射器存储路径以及截断数为参数"""
    # 使用get_data_labels函数获取简单处理后的训练数据和标签
    train_data, train_labels = get_data_labels(csv_path)
    # 进行正负样本均衡切割, 使其数量比例为1:1
    train_data = train_data[:-cut_num]
    train_labels = train_labels[:-cut_num]
    # 实例化一个词汇映射器对象
    t = Tokenizer(num_words=None, char_level=False)  #num_words:None或整数,处理的最大单词数量。少于此数的单词丢掉
    # 使用映射器拟合现有文本数据
    t.fit_on_texts(train_data)   #使用一系列文档来生成token词典，texts为list类，每个元素为一个文档
    # 使用joblib工具保存映射器
    joblib.dump(t, tokenizer_path)
    # 使用映射器转化现有文本数据
    x_train = t.texts_to_sequences(train_data)  #texts_to_sequences(texts) 得到词索引
    # 获得标签数据
    y_train = train_labels
    return x_train, y_train
